#!/usr/bin/env ruby

#%# family=auto
#%# capabilities=autoconf suggest

require 'English'
require 'strscan'

label = ENV["label"]

command = ARGV.shift

def autoconf
  query_log_path_variable_names = [
    "query_log_path",
    "log_path", # For backward compatibility. Remove me when 5.0.0.
    "http_query_log_path",
    "httpd_query_log_path",
    "gqtp_query_log_path",
  ]
  query_log_paths = query_log_path_variable_names.collect do |variable_name|
    ENV[variable_name]
  end
  query_log_paths = query_log_paths.compact
  if query_log_paths.empty?
    variable_names = query_log_path_variable_names.collect do |name|
      "env.#{name}"
    end
    variable_names_label = variable_names[0..-2].join(", ")
    variable_names_label << " and/or #{variable_names.last}"
    puts("no (No query log path is specified. Specify #{variable_names_label}.)")
    exit(false)
  end
  query_log_paths.each do |query_log_path|
    next unless File.exist?(query_log_path)
    puts("yes")
    exit(true)
  end
  query_log_paths_label = query_log_paths.join(", ")
  puts("no (All query log paths don't exist: #{query_log_paths_label})")
  exit(false)
end

def suggest
  env_path_exist_p = lambda do |variable_name|
    path = ENV[variable_name]
    path and File.exist?(path)
  end
  exist_p = lambda do |query_log_path_name, pid_file_path_name|
    env_path_exist_p.call(query_log_path_name) and
      env_path_exist_p.call(pid_file_path_name)
  end
  if exist_p.call("http_query_log_path", "http_pid_path")
    puts("http")
  end
  if exist_p.call("httpd_query_log_path", "httpd_pid_path")
    puts("httpd")
  end
  if exist_p.call("gqtp_query_log_path", "gqtp_pid_path")
    puts("gqtp")
  end
  exit(true)
end

def target_query_log_path
  if /_([^_]+)\z/ =~ $0
    service_type = $1
    query_log_path_variable_name = "#{service_type}_query_log_path"
    query_log_path = ENV[query_log_path_variable_name]
  else
    query_log_path_variable_name = "query_log_path"
    query_log_path = ENV[query_log_path_variable_name]
    # For backward compatibility. Remove me when 5.0.0.
    query_log_path ||= ENV["log_file"]
  end

  if query_log_path.nil?
    key = "env.#{query_log_path_variable_name}"
    $stderr.puts("Query log path isn't specified by #{key}")
    exit(false)
  end

  unless File.exist?(query_log_path)
    $stderr.puts("Query log path doesn't exist: #{query_log_path}")
    exit(false)
  end

  query_log_path
end

class ReverseLineReader
  def initialize(io)
    @io = io
    @io.seek(0, IO::SEEK_END)
    @buffer = ""
    @data = ""
  end

  def each
    separator = $/
    separator_length = separator.length
    while read_to_buffer
      loop do
        index = @buffer.rindex(separator, @buffer.length - 1 - separator_length)
        break if index.nil? or index.zero?
        last_line = @buffer.slice!((index + separator_length)..-1)
        yield(last_line)
      end
    end
    yield(@buffer) unless @buffer.empty?
  end

  private
  BYTES_PER_READ = 4096
  def read
    position = @io.pos
    if position < BYTES_PER_READ
      bytes_per_read = position
    else
      bytes_per_read = BYTES_PER_READ
    end

    if bytes_per_read.zero?
      @data.replace("")
    else
      @io.seek(-bytes_per_read, IO::SEEK_CUR)
      @io.read(bytes_per_read, @data)
      @io.seek(-bytes_per_read, IO::SEEK_CUR)
    end

    @data
  end

  def read_to_buffer
    data = read
    if data.empty?
      false
    else
      @buffer.insert(0, data)
      true
    end
  end
end

case command
when "autoconf", "detect"
  autoconf
when "suggest"
  suggest
when "config"
  if label.nil?
    title = "groonga: query performance"
  else
    title = "groonga: #{label}: query performance"
  end
  puts(<<EOF)
graph_title #{title}
graph_vlabel seconds
graph_category groonga
graph_info groonga query performance

longest.label Longest
average.label Average
median.label Median
EOF
  exit(true)
end

span = 60 * 5 # 5min
mega = 1_000_000.0
now = Time.now
elapsed_times = []
File.open(target_query_log_path) do |log_file|
  ReverseLineReader.new(log_file).each do |line|
    case line
    when /\A(\d+)-(\d+)-(\d+) (\d+):(\d+):(\d+)\.(\d+)\|((?:0x)?[\da-f]+)\|<(\d+) rc=0$/
      _, year, month, day, hour, minutes, seconds, milliseconds,
        context, elapsed = $LAST_MATCH_INFO.to_a
      time_stamp = Time.local(year, month, day,
                              hour, minutes, seconds, milliseconds)
      difference = now - time_stamp
      break if difference > span
      elapsed_in_micro_seconds = elapsed.to_i / mega
      elapsed_times << elapsed_in_micro_seconds
    end
  end
end

sorted_elapsed_times = elapsed_times.sort
if sorted_elapsed_times.empty?
  longest = 0
  average = 0
  median = 0
else
  longest = sorted_elapsed_times.last
  average = sorted_elapsed_times.inject(&:+) / sorted_elapsed_times.size.to_f
  median = sorted_elapsed_times[sorted_elapsed_times.size / 2]
end

puts("longest.value #{longest}")
puts("average.value #{average}")
puts("median.value #{median}")
